package com.andox.fn.inf.rpc.rest.common;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;

import com.andox.fn.inf.rpc.rest.RestControllerMark;
import com.andox.fn.inf.rpc.rest.util.ClassHelper;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.LoaderClassPath;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.ParameterAnnotationsAttribute;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.ArrayMemberValue;
import javassist.bytecode.annotation.IntegerMemberValue;
import javassist.bytecode.annotation.StringMemberValue;

/**   
 * @ClassName:   AdaptorWeave   
 * @author:      Andox
 * @date:        2019年7月29日 下午2:22:07   
 *     
 */
public class TranslatorWeaver {
	public static final String HEARTBEAT = "heartbeat";
	
	public TranslatorWeaver() {
	}
	
	public static void main(String[] args) throws Exception{
		TranslatorWeaver aw = new TranslatorWeaver();
		aw.test();
		Class<?> x = aw.compileSpringController(RestControllerMark.class);
		System.out.println(x.getName());
		
		System.err.println(x.isAnnotationPresent(RestController.class));
		for (Method m : x.getMethods()) {
			if (m.getName().startsWith("api")) {
				System.out.println(m.isAnnotationPresent(RequestMapping.class));
				RequestMapping ann = m.getAnnotation(RequestMapping.class);
				System.out.println(JSON.toJSONString(ann.value()));
			}
		}
	}
	
	@SuppressWarnings("unused")
	private void test() throws Exception{
		ClassPool pool = new ClassPool(true);

        // create the class
        CtClass cc = pool.makeClass("foo");
        ClassFile ccFile = cc.getClassFile();
        ConstPool constpool = ccFile.getConstPool();

        // create the annotation
        AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
        Annotation annot = new Annotation(RequestMapping.class.getName(), constpool);
        annot.addMemberValue("value", new IntegerMemberValue(ccFile.getConstPool(), 0));
        attr.addAnnotation(annot);

        // create the method
        CtMethod mthd = CtNewMethod.make("public Integer getInteger() { return null; }", cc);
        cc.addMethod(mthd);
        mthd.getMethodInfo().addAttribute(attr);

        //cc.writeFile("./");
        // generate the class
        Class<?> clazz = cc.toClass();

        Method method = clazz.getMethod("getInteger");
        System.out.println("----" + method.isAnnotationPresent(RequestMapping.class) + "----");
        // length is zero
        java.lang.annotation.Annotation[] annots = clazz.getAnnotations();
	}
	
	
	public Class<?> compileSpringController(Class<?> interfaceClass){
		String className = "RestController$" + interfaceClass.getName();
		
		ClassPool pool = ClassPool.getDefault();
		importContent(pool);
		CtClass cls = implementsClass(className, pool);
		if (!cls.isFrozen()) {
			fieldClass(cls);
			methodClass_invoke(cls);
			methodClass_setter(cls);
			heartbeat(cls, interfaceClass);
			
			Method[] r = interfaceClass.getDeclaredMethods();
			for (Method m : r) {
				if (!m.getDeclaringClass().equals(Object.class)) {
					methodClass(cls, interfaceClass, m);
				}
			}
		}
		try {
			Class<?> clazz = cls.toClass(ClassHelper.getCallerClassLoader(getClass()), null);
			return clazz;
		} catch (CannotCompileException e) {
			throw new RuntimeException(e);
		}
	}
	
	private void importContent(ClassPool pool) {
		pool.appendClassPath(new LoaderClassPath(ClassHelper.getCallerClassLoader(getClass())));
		
		List<String> importList = Lists.newArrayList();
		importList.add("import java.lang.reflect.InvocationTargetException;\n"); 
		importList.add("import java.lang.reflect.Method;\n");
		importList.add("import java.lang.reflect.Parameter;\n"); 
		importList.add("import java.net.InetAddress;\n");
		importList.add("import java.net.UnknownHostException;\n"); 
		importList.add("import java.util.List;\n");
		importList.add("import java.util.Map;\n"); 
		importList.add("import javax.servlet.http.HttpServletRequest;\n"); 
		importList.add("import javax.servlet.http.HttpServletResponse;\n"); 
		importList.add("import com.google.common.collect.Lists;\n"); 
		importList.add("import com.google.common.collect.Maps;\n"); 
		importList.add("import org.slf4j.Logger;\n");
		importList.add("import org.slf4j.LoggerFactory;\n"); 
		importList.add("import org.springframework.beans.factory.annotation.Autowired;\n"); 
		importList.add("import org.springframework.core.LocalVariableTableParameterNameDiscoverer;\n");
		importList.add("import org.springframework.core.convert.support.GenericConversionService;\n"); 
		importList.add("import org.springframework.web.bind.annotation.RequestBody;\n");
		importList.add("import org.springframework.web.bind.annotation.RequestMapping;\n"); 
		importList.add("import org.springframework.web.bind.annotation.RequestMethod;\n"); 
		importList.add("import org.springframework.web.bind.annotation.RestController;\n"); 
		importList.add("import com.alibaba.fastjson.JSON;\n");
		importList.add("import com.andox.fn.inf.rpc.rest.RestControllerMark;\n"); 
		importList.add("import com.andox.fn.inf.rpc.rest.RpcRequest;\n");
		importList.add("import com.andox.fn.inf.rpc.rest.RpcResponse;");
		importList.add("import com.andox.fn.inf.rpc.rest.exception.ServiceRuntimeException;");
		importList.add("import com.andox.fn.inf.rpc.rest.proxy.RestServiceWrapper;");
		
		for (String i : importList) {
			String packageName = i;
			packageName = packageName.substring(6).trim().replace(";", "");
			pool.importPackage(packageName);
		}
	}
	
	private CtClass implementsClass(String className, ClassPool pool) {
		//pool.getOrNull(classname)getCtClass(className);
		CtClass cc = pool.getOrNull(className);
		if (cc != null)
			return cc;
		
		CtClass cls = pool.makeClass(className);
		CtClass i = pool.makeClass(RestControllerMark.class.getName());
		cls.addInterface(i);
		
	    ConstPool constpool = cls.getClassFile().getConstPool();
	    
		AnnotationsAttribute classAttr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
        Annotation controller = new Annotation("org.springframework.web.bind.annotation.RestController", constpool);
        classAttr.addAnnotation(controller);
        cls.getClassFile().addAttribute(classAttr);
		return cls;
	}
	
	private void fieldClass(CtClass cls) {
		try {
			cls.addField(CtField.make("final static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(\"inf-rpc-translator\");", cls));
			cls.addField(CtField.make("Object targetObject = null;", cls));
			cls.addField(CtField.make("Class interfaceClass = null;", cls));
			cls.addField(CtField.make("RestServiceWrapper serviceWrapper = null;", cls));
			cls.addField(CtField.make("Map methodCache = new java.util.concurrent.ConcurrentHashMap();", cls));
			cls.addField(CtField.make("org.springframework.core.LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer = new org.springframework.core.LocalVariableTableParameterNameDiscoverer();", cls));
			
			CtField f = CtField.make("org.springframework.core.convert.support.GenericConversionService conversionService;", cls);
			// 属性附上注解
			FieldInfo fieldInfo = f.getFieldInfo();
	        AnnotationsAttribute fieldAttr = new AnnotationsAttribute(cls.getClassFile().getConstPool(), AnnotationsAttribute.visibleTag);
	        Annotation autowired = new Annotation("org.springframework.beans.factory.annotation.Autowired",cls.getClassFile().getConstPool());
	        fieldAttr.addAnnotation(autowired);
	        fieldInfo.addAttribute(fieldAttr);
	        cls.addField(f);
		} catch (CannotCompileException e) {
			throw new RuntimeException(e);
		}
	}
	
	private void methodClass(CtClass cls, Class<?> clazz, Method method) {
		String url = clazz.getName() + "/" + method.getName();
		Parameter[] pas = method.getParameters();
		for (int i = 0; i < pas.length; i++) {
			Parameter pa = pas[i];
			pa.getType();
			url = url + "/" + pa.getType().getTypeName();
		}
		
		String x = String.format( 
				"	public RpcResponse api_%s(RpcRequest invokerParams, javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) {\n" + 
				"		RpcResponse invoke = invoke(invokerParams, request);\n" + 
				"		logger.debug(\"callback :\" + invoke);\n" + 
				"		return invoke;\n" + 
				"	 }\n", url.replace("/", "_").replace(".", "_"));
		try {
			
			ConstPool constpool = cls.getClassFile().getConstPool();
		    
			AnnotationsAttribute methodAttr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
	        Annotation requestMapping = new Annotation("org.springframework.web.bind.annotation.RequestMapping", constpool);
	        ArrayMemberValue a =new ArrayMemberValue(new StringMemberValue(url,constpool), constpool);
	        a.setValue(new StringMemberValue[] {new StringMemberValue(url, constpool)});
	        requestMapping.addMemberValue("value", a);
	        methodAttr.addAnnotation(requestMapping);
	        
	        ParameterAnnotationsAttribute parameterAtrribute = new ParameterAnnotationsAttribute(
	                cls.getClassFile().getConstPool(), ParameterAnnotationsAttribute.visibleTag);
	        Annotation paramAnnot = new Annotation("org.springframework.web.bind.annotation.RequestBody", cls.getClassFile().getConstPool());
	        Annotation paramx = new Annotation("javax.validation.constraints.NotNull", cls.getClassFile().getConstPool());
	        Annotation[][] paramArrays = new Annotation[3][1];
	        paramArrays[0][0] = paramAnnot;
	        paramArrays[1][0] = paramx;
	        paramArrays[2][0] = paramx;
	        parameterAtrribute.setAnnotations(paramArrays);
	        
	        
	        CtMethod ctMethod = CtNewMethod.make(x, cls);
	        ctMethod.getMethodInfo().addAttribute(methodAttr);
	        ctMethod.getMethodInfo().addAttribute(parameterAtrribute);
	        cls.addMethod(ctMethod);
		} catch (CannotCompileException e) {
			throw new RuntimeException(e);
		}
	}
	
	private void heartbeat(CtClass cls, Class<?> clazz) {
		String url = clazz.getName() + "/" + HEARTBEAT;
		String x = String.format( 
				"	public RpcResponse api_%s(RpcRequest invokerParams, javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) {\n" + 
				"		RpcResponse invoke = invoke(invokerParams, request);\n" + 
				"		logger.info(\"callback :\" + invoke);\n" + 
				"		return invoke;\n" + 
				"	 }\n", url.replace("/", "_").replace(".", "_"));
		try {
			
			ConstPool constpool = cls.getClassFile().getConstPool();
		    
			AnnotationsAttribute methodAttr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
	        Annotation requestMapping = new Annotation("org.springframework.web.bind.annotation.RequestMapping", constpool);
	        ArrayMemberValue a =new ArrayMemberValue(new StringMemberValue(url,constpool), constpool);
	        a.setValue(new StringMemberValue[] {new StringMemberValue(url, constpool)});
	        requestMapping.addMemberValue("value", a);
	        methodAttr.addAnnotation(requestMapping);
	        
	        ParameterAnnotationsAttribute parameterAtrribute = new ParameterAnnotationsAttribute(
	                cls.getClassFile().getConstPool(), ParameterAnnotationsAttribute.visibleTag);
	        Annotation paramAnnot = new Annotation("org.springframework.web.bind.annotation.RequestBody", cls.getClassFile().getConstPool());
	        Annotation paramx = new Annotation("javax.validation.constraints.NotNull", cls.getClassFile().getConstPool());
	        Annotation[][] paramArrays = new Annotation[3][1];
	        paramArrays[0][0] = paramAnnot;
	        paramArrays[1][0] = paramx;
	        paramArrays[2][0] = paramx;
	        parameterAtrribute.setAnnotations(paramArrays);
	        
	        
	        CtMethod ctMethod = CtNewMethod.make(x, cls);
	        ctMethod.getMethodInfo().addAttribute(methodAttr);
	        ctMethod.getMethodInfo().addAttribute(parameterAtrribute);
	        cls.addMethod(ctMethod);
		} catch (CannotCompileException e) {
			throw new RuntimeException(e);
		}
	}
	
	
	private void methodClass_invoke(CtClass cls) {
		String x = 
				"	private RpcResponse invoke(RpcRequest ar, HttpServletRequest request) {\n" + 
				"		return serviceWrapper.invoke(ar, request);			"+
				"	}\n";
		try {
			cls.addMethod(CtNewMethod.make(x, cls));
		} catch (CannotCompileException e) {
			throw new RuntimeException(e);
		}
	}
	
	private void methodClass_setter(CtClass cls) {
		String x = 
				"	public void setServiceWrapper(RestServiceWrapper serviceWrapper) {\n" + 
				"		this.serviceWrapper = serviceWrapper;			"+
				"	}\n";
		try {
			cls.addMethod(CtNewMethod.make(x, cls));
		} catch (CannotCompileException e) {
			throw new RuntimeException(e);
		}
	}
}
